﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Diagnostics;

namespace Mimoza.Transport
{
    public class Dispatcher
    {
        public Dispatcher()
        {            
        }

        public void AddConnection(Connection connection)
        {
            lock (m_conns)
            {
                m_conns.Add(connection);
                m_selectConns.Add(connection);

                Common.Logger.Log.Info("Dispatcher.Add count - " + m_conns.Count + ".");
            }
        }

        public void RemoveConnection(Connection connection)
        {
            lock (m_conns)
            {
                m_conns.Remove(connection);
                m_selectConns.Remove(connection);

                connection.Close();

                Common.Logger.Log.Info("Dispatcher.RemoveConnection count - " + m_conns.Count + ".");
            }
        }

        public void AddListener(Listener listener)
        {
            lock (m_listeners)
            {
                m_listeners.Add(listener);
                listener.StartAccept(new Listener.ConnectionCreatedNotifier(OnAcceptConnection));

                Common.Logger.Log.Info("Dispatcher.AddLListener count - " + m_listeners.Count + ".");
            }
        }

        public void RemoveListener(Listener listener)
        {
            lock (m_listeners)
            {
                m_listeners.Remove(listener);

                listener.Close();

                Common.Logger.Log.Info("Dispatcher.RemoveListener count - " + m_listeners.Count + ".");
            }
        }

        public void RegisterTransferObject(TransferObject obj)
        {
            lock (m_regTransferObjectStubs)
            {
                if (!m_regTransferObjectStubs.ContainsKey(obj.TypeName))
                {
                    m_regTransferObjectStubs.Add(obj.TypeName, obj);
                }

                Common.Logger.Log.Trace("Dispatcher.RegisterTransferObject type - '" + obj.TypeName 
                    + "' count - " + m_regTransferObjectStubs.Count + ".");
            }
        }

        public void RegisterHandler(string typeName, Transport.Handler handler)
        {
            lock (m_regTransferObjectHandler)
            {
                m_regTransferObjectHandler[typeName] += handler;

                int subsSize = m_regTransferObjectHandler[typeName].GetInvocationList().Length;
                Common.Logger.Log.Trace("RegisterTransferObject type - '" + typeName
                    + "' subs count - " + subsSize.ToString() + " count - "
                    + m_regTransferObjectHandler.Count.ToString() + ".");
            }
        }

        public TransferObject GetStubTransferObject(string typeName)
        {
            lock (m_regTransferObjectStubs)
            {
                TransferObject trObj = m_regTransferObjectStubs.ContainsKey(typeName)?
                    m_regTransferObjectStubs[typeName]:null;

                Debug.Assert(trObj != null, "Transfer object (" + typeName + "') is not registred in Transport.");

                return trObj;
            }
        }

        public Transport.Handler GetTransferObjectHandler(string typeName)
        {
            lock (m_regTransferObjectHandler)
            {
                return m_regTransferObjectHandler.ContainsKey(typeName) ?
                    m_regTransferObjectHandler[typeName] : null;
            }
        }
                
        public void DoProcess()
        {            
            ArrayList sockets = GetSelectSockets();
            ArrayList errorSockets = GetSelectSockets();

            if (sockets.Count == 0)
            {
                Thread.Sleep(100);
                return;
            }

            try
            {
                Socket.Select(sockets, null, errorSockets, 1000);
            }
            catch (System.Exception e)
            {
                Common.Logger.Log.Warn("Dispatcher.DoProcess error occured on Socket.Select operation - " + 
                    e.ToString() + "'.");
            }

            if (sockets.Count == 0) return;

            Connection readyConn = ProcessReadySocket(sockets);
            if (readyConn == null)
            {
                return;
            }

            do
            {
                ProcessReadyConnection(readyConn);
            }
            while (readyConn.HasUnprocessedData);

            ReturnConnection(readyConn);
        }

        void ProcessReadyConnection(Connection conn)
        {
            IPEndPoint endPoint = null;
            
            try
            {
                Packet requestPacket;
                                
                conn.Recieve(out requestPacket, out endPoint);

                Packet replyData = null;

                TransferObject userReqStub = GetStubTransferObject(requestPacket.UserDataTypeName);
                if (userReqStub != null)
                {
                    Common.Logger.Log.Debug("Dispatcher.ProcessReadyConnection connection - " + conn.RemoteAddress + ".");

                    TransferObject userReq = requestPacket.RestoreUserData(userReqStub);
                    userReq.RemoteAddress = (endPoint!=null)?endPoint.Address.ToString():"";

                    TransferObject userReply = null;
                    ProcessRequest(conn, userReq, out userReply);

                    if (userReply != null)
                    {
                        replyData = new Packet(userReply);
                    }

                    Common.Logger.Log.Debug("Dispatcher.ProcessReadyConnection (has reply - " + (replyData != null).ToString() + ")...OK");
                }
                else
                {
                    Common.Logger.Log.Error("Dispatcher.ProcessReadyConnection error request isn't processed. connection - " + conn.RemoteAddress + ".");
                }

                if (replyData != null)
                {
                    conn.Send(replyData, endPoint);
                }
            }
            catch (Exception e)
            {
                Common.Logger.Log.Error("Dispatcher.ProcessReadyConnection error occured on connection - " + 
                    conn.RemoteAddress + ".\nError - '" + e.ToString() + "'.");

                bool bNeedToRemove = false;
                if (e is System.Net.Sockets.SocketException)
                {
                    Common.Logger.Log.Error("Dispatcher.ProcessReadyConnection error on communication lvele. Close connection.");
                    bNeedToRemove = true;
                }
                else
                {
                    // prepare reply with exception
                    try
                    {
                        conn.Send(new Packet(new ErrorReply(e)), 
                            endPoint);
                    }
                    catch (Exception eInner)
                    {
                        Common.Logger.Log.Error("Dispatcher.ProcessReadyConnection error occured in reply send Error - '" 
                            + eInner.ToString() + "'.");
                        bNeedToRemove = true;
                    }

                }

                if (bNeedToRemove)
                {
                    RemoveConnection(conn);
                }

                //Console.WriteLine(e.ToString());                
            }
        }

        ArrayList GetSelectSockets()
        {
            ArrayList sockets = new ArrayList();
            lock (m_conns)
            {
                foreach (Connection conn in m_selectConns)
                {
                    if (!conn.IsClosed())
                    {
                        sockets.Add(conn.Socket);
                    }
                }
            }

            return sockets;
        }

        Connection ProcessReadySocket(ArrayList readySockets)
        {
            lock (m_conns)
            {
                foreach (Connection conn in m_selectConns)
                {
                    if (readySockets.Contains(conn.Socket))
                    {
                        m_selectConns.Remove(conn);
                        return conn;
                    }
                }
            }

            return null;
        }

        void ReturnConnection(Connection conn)
        {
            lock (m_conns)
            {
                if (m_conns.Contains(conn))
                {
                    m_selectConns.Add(conn);
                }
            }
        }

        void OnAcceptConnection(Connection conn)
        {
            if (conn != null)
            {
                AddConnection(conn);
            }
        }

        void ProcessRequest(Connection conn, TransferObject userReq, out TransferObject userReply)
        {
            userReply = null;
            userReq.ProcessRequest(conn, out userReply);

            Transport.Handler handler = GetTransferObjectHandler(userReq.TypeName);
            if (handler != null)
            {
                TransferObject handlerReply= null;

                handler(userReq, out handlerReply);

                if (userReply == null && handlerReply != null)
                {
                    userReply = handlerReply;

                    Common.Logger.Log.Debug("Dispatcher.ProcessRequest save handler reply type name - '" + userReq.TypeName + "'.");

                }
            }
        }
                
        List<Connection> m_conns = new List<Connection>();
        List<Connection> m_selectConns = new List<Connection>();

        List<Listener> m_listeners = new List<Listener>();

        Dictionary<string, TransferObject> m_regTransferObjectStubs = new Dictionary<string, TransferObject>();
        Dictionary<string, Transport.Handler> m_regTransferObjectHandler = 
            new Dictionary<string, Transport.Handler>();
    }
}
